#include <math.h>

#include "mathutils.h"
#include "synmixer.h"
#include "synmidi.h"
#include "synfx.h"
#include "synblepbuffer.h"

Synth::Mixer mixer;

namespace Synth {

/*
Parameter Space
Voice local inputs:
  *Velocity
  *ADSR1,2
  *LFO1,2
  *Note

Voice local parameters:
  *Osc xpose, adsr settings

Channel local parameters:
  *Dist
  *FX (chorus flanger)
  *Channel compressor

Global parameters:
  *

*/

float parameterTable[ParameterTableSize];
int parameterTableInt[ParameterTableSize];

const short left_reverb_delays[6] = {
	1693 * OVERSAMPLE_FACTOR,
	1827 * OVERSAMPLE_FACTOR,
	1965 * OVERSAMPLE_FACTOR,
	2043 * OVERSAMPLE_FACTOR,
	2175 * OVERSAMPLE_FACTOR,
	2357 * OVERSAMPLE_FACTOR,
};

const short right_reverb_delays[6] = {
	(1693+20) * OVERSAMPLE_FACTOR, 
	(1827-40) * OVERSAMPLE_FACTOR, 
	(1965+83) * OVERSAMPLE_FACTOR, 
	(2043-42) * OVERSAMPLE_FACTOR, 
	(2175+101) * OVERSAMPLE_FACTOR, 
	(2357-85) * OVERSAMPLE_FACTOR,
};

Mixer::Mixer() {
	memset(globalParams, 0, sizeof(globalParams));
	lreverb.Init(left_reverb_delays);
	rreverb.Init(right_reverb_delays);
	ldelay.setSize(6000);
	rdelay.setSize(6000);

	comp.initRuntime();
	BlepInit();
}

void Mixer::Init(const unsigned char *ptr) {
	for (int i = 0; i < 16; i++) {
		memcpy(channels[i].params, ptr, cp_NumChannelParams);
		ptr += cp_NumChannelParams;
	}
	memcpy(globalParams, ptr, gp_NumGlobalParams);
}

void Mixer::ResetGlobalParameters() {
	for (int i = 0; i < gp_NumGlobalParams; i++) {
		parameterTable[gp_Volume + i] = (float)globalParams[i] / 127.0f;
		parameterTableInt[gp_Volume + i] = globalParams[i];
	}
}

int Mixer::GetNumActiveVoicesFromChannel(int chan) const {
	int count = 0;
	for (int i = 0; i < VOICEPOOLSIZE; i++) {
		if (voicepool[i].GetChannel() == &channels[chan])
			count++;
	}
	return count;
}

Voice *Mixer::AllocVoice(Channel *chan, int note) {
	if (chan->params[cp_Polyphony] == 1) {
		for (int v = 0; v < VOICEPOOLSIZE; v++) {
			if (voicepool[v].GetChannel() == chan) {
				return &voicepool[v];
			}
		}
	}

	//if (chan->no != 3)
	//	return 0;
	int bestScore = 0;
	int bestVoice = -1;
	for (int v = 0; v < VOICEPOOLSIZE; v++) {
		if (!voicepool[v].IsPlaying()) {
			//Ah excellent, a free voice.
			return &voicepool[v];
		} else {
			int vs = voicepool[v].GetScore(chan, note);
			if (vs > bestScore) {
				bestScore = vs;
				bestVoice = v;
			}
		}
	}
	// No empty voice?
	// grab the oldest!
	voicepool[bestVoice].StopHard();
	return &voicepool[bestVoice];
}

void Mixer::StopVoice(const Channel *chan, int note) {
	for (int v = 0; v < VOICEPOOLSIZE; v++) {
		if (voicepool[v].GetChannel() == chan && voicepool[v].GetPlayingNote() == note)
			voicepool[v].Stop();
	}
}

void Mixer::StopVoiceGroup(const Channel *chan, int group) {
	for (int v = 0; v < VOICEPOOLSIZE; v++) {
		if (voicepool[v].GetChannel() == chan && voicepool[v].group == group)
			voicepool[v].Stop();
	}
}

void Mixer::Render(Block leftBuf, Block rightBuf) {
	Block lR, rR;
	Block lD, rD;
	Block lTemp, rTemp;
	
	Clear(leftBuf);
	Clear(rightBuf);
	Clear(lR);
	Clear(rR);
	Clear(lD);
	Clear(rD);
	
	ResetGlobalParameters();

	// Loop over channels
	for (int c = 0; c < 16; c++) {
		channels[c].no = c;
		channels[c].ResetChannelParameters();
		channels[c].DoModulations();

		Block tempL, tempR;
		Clear(tempL);
		Clear(tempR);

		for (int v = 0; v < VOICEPOOLSIZE; v++) {
			if (voicepool[v].GetChannel() == &channels[c]) {
				voicepool[v].ResetVoiceParameters();
				voicepool[v].RenderAdd(tempL, tempR);
			}
		}

		Clear(lTemp);
		Clear(rTemp);
		channels[c].RenderChannelFX(tempL, tempR, lTemp, rTemp);
#ifndef STREAMLINE
		VU[c][0] *= 0.992f;
		VU[c][1] *= 0.992f;
		FORBLOCK(i) {
			if (fabsf(lTemp[i]) > VU[c][0])
				VU[c][0] = fabsf(lTemp[i]);
			if (fabsf(rTemp[i]) > VU[c][1])
				VU[c][1] = fabsf(rTemp[i]);
		}
#endif

		AddScaled(lR, lTemp, parameterTable[cp_ReverbSend]);
		AddScaled(rR, rTemp, parameterTable[cp_ReverbSend]);
		AddScaled(lD, lTemp, parameterTable[cp_DelaySend]);
		AddScaled(rD, rTemp, parameterTable[cp_DelaySend]);

		AddScaled(leftBuf, lTemp, parameterTable[cp_ChannelVolume]);
		AddScaled(rightBuf, rTemp, parameterTable[cp_ChannelVolume]);
	}

	float hl = parameterTable[gp_ReverbLength];
	hl *= hl;  // square curve feels much better
	hl *= 65536;

	lreverb.Render(lR, hl, parameterTable[gp_ReverbPole]);
	rreverb.Render(rR, hl, parameterTable[gp_ReverbPole]);

	ldelay.Render(lD, parameterTable[gp_DelayAmount], parameterTable[gp_DelayFeedback]);
	rdelay.Render(rD, parameterTable[gp_DelayAmount], parameterTable[gp_DelayFeedback]);

	PanVolume(leftBuf, rightBuf, lD, rD, 1.0f, parameterTable[gp_DelayPan]);

	AddScaled(leftBuf, lR, parameterTable[gp_ReverbVolume]);
	AddScaled(rightBuf, rR, parameterTable[gp_ReverbVolume]);
	switch (parameterTableInt[gp_GlobalCompressorMode]) {
	case 0:
		break;
	default:
		{
			comp.threshdB = -24 + 24*parameterTable[gp_GlobalCompressorThreshold];
			comp.ratio = parameterTable[gp_GlobalCompressorRatio];
			comp.setAttack(parameterTable[gp_GlobalCompressorAttack] * 127);
			comp.setRelease(parameterTable[gp_GlobalCompressorRelease] * 127);
			float makeup = 1 + parameterTable[gp_GlobalCompressorMakeupGain];
			comp.Render(leftBuf, rightBuf, makeup, parameterTableInt[gp_GlobalCompressorMode] == 2);
			break;
		}
	}

	// OK, let's clamp!
#ifndef STREAMLINE
	vu[0] *= 0.992f;
	vu[1] *= 0.992f;
	FORBLOCK(i)
	{
		if (fabsf(leftBuf[i]) > vu[0])
			vu[0] = fabsf(leftBuf[i]);
		if (fabsf(rightBuf[i]) > vu[1])
			vu[1] = fabsf(rightBuf[i]);
	}
#endif
	FORBLOCK(i)
	{
		if (leftBuf[i] > 1.0f) leftBuf[i] = 1.0f;
		if (leftBuf[i] < -1.0f) leftBuf[i] = -1.0f;
		if (rightBuf[i] > 1.0f) rightBuf[i] = 1.0f;
		if (rightBuf[i] < -1.0f) rightBuf[i] = -1.0f;
	}

	timeStamp++;
}

void Mixer::RenderOversample(Block left, Block right)
{
	for (int j = 0; j < OVERSAMPLE_FACTOR; j++) {
		Block raw_left, raw_right;
		Render(raw_left, raw_right);
		ds_l.Process(raw_left, left, j);
		ds_r.Process(raw_right, right, j);
	}
}

#ifndef STREAMLINE
void Mixer::DoMIDICmd(const unsigned char *cmd) {
	int channel = cmd[0] & 0xF;
	switch (cmd[0] >> 4) {
	case 0x9: // NOTE ON
		{
			int key = cmd[1];
			int vel = cmd[2];
			if (vel) {
				channels[channel].NoteOn(key, vel);
				break;
			}
		}
		//allow fall through
	case 0x8: //NOTE OFF
		{
			int key = cmd[1];
			channels[channel].NoteOff(key);
		}
		break;
	case 0xb: //CONTROLLER
		{
			int cont = cmd[1];
			int val = cmd[2];
		}
		break;
	case 0xf:
		break;
	}
}
#endif

void Mixer::DoFakeMIDICmd(int channel, int command, int value1, int value2) {
	switch (command) {
	case ME_KEYDOWN:
		channels[channel].NoteOn(value1, value2);
		break;
	case ME_KEYUP:
		channels[channel].NoteOff(value1);
		break;
	default: // must be a controller
		channels[channel].ControllerChange(command, value1);
		break;
	}
}

void Mixer::StopAllVoices() {
	for (int i = 0; i < VOICEPOOLSIZE; i++) {
		voicepool[i].StopHard();
	}
}

}